package outline.forweb;

import org.agora.games.bodybuilder.Pixel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import svg.parser.Vertex;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class ImageOutlineFinder {
    public final String TAG = this.getClass().getSimpleName();
    private static Logger logger = LoggerFactory.getLogger(ImageOutlineFinder.class);

    BufferedImage img = null;

    private ArrayList<Vertex> points = new ArrayList<Vertex>();
    private ArrayList<Vertex> averagedPoints = new ArrayList<Vertex>();
    private static final double LINE_LENGTH = 20;
    private String image;
    
    public ArrayList<Vertex> getPoints() {

        return points;
    }

    public ArrayList<Vertex> findBorder() {
        points = new ArrayList<Vertex>();
        averagedPoints = new ArrayList<Vertex>();
        loopOverImage();
        return points;
    }
    
    public ImageOutlineFinder(){
    }
    
    public String getImage(){
        return image;
    }

    public void setImage(String path) {
        this.image = path;
        // Load the image from the file
        try {
            img = ImageIO.read(new File(path));
        } catch (IOException e) {
            logger.debug("ImageOutlineFinder", "image not found: {}", e.toString());
        }
//		BufferedImage invertedImage = new BufferedImage(
//						img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB);
//
        // The image will be loaded inverted - invert the image

//		Graphics2D graphics2D = invertedImage.createGraphics();
//		AffineTransform at = new AffineTransform();
//		at.scale(1, -1);
//		at.translate(0, -img.getHeight());
//
//		graphics2D.drawImage(img, at, null);
//		graphics2D.dispose();
//
//		img = invertedImage;


    }

    private int loopOverImage() {
        if (img != null) {
            // Loop until we find a boundary pixel
            for (int cx = 0; cx < img.getWidth(); cx++) {
                for (int cy = 0; cy < img.getHeight(); cy++) {
                    if (isBoundary(cx, cy)) {
                        logger.debug("first point is {} {}", cx, cy);

                        traverseBoundary(cx, cy);
                        return 0;
                    }
                }
            }
        } else {
            logger.error("loopOverImage", "image null");
        }
        return 0;
    }

    private void traverseBoundary(int x, int y) {

        Pixel start = new Pixel(x, y, Pixel.WEST, Pixel.CENTRE);
        Pixel next = start;
        Pixel old;

        points.add(start.toVertex());

        // Set arbitary limit to length of circumference
        for (int i = 0; i < 10000000; i++) {
            // Save off the last position
            old = next;

            // Find the next clockwise boundary pixel
            next = findNextBounearyPoint(next);
            if (next == null) {

            }

            // Set the search start point as the previous pixel
            // to avoid loops and continue traversing the shape
            // in the same direction i.e.

            // edge:  x  first point     second point  34x
            //       x            23x                  2x
            //      x             1x                   1

            // In this diagram the numbers represent the order
            // in which the neighboring points are tested. We
            // Always start with the previous point found.
            next.setNSP(old.sub(next));

            // When we get back to the start break the loop
            if (next.equals(start))
                break;

            points.add(next.toVertex());

        }
    }


    private Pixel findNextBounearyPoint(Pixel p) {
        // Move clockwise around from previous point until we
        // hit another boundary cell
        for (int i = 0; i < 9; i++) {
            // Find the next pixel clockwise - the pixel remembers the last
            // position tested and will increment to the next position
            Pixel next = p.clockwise();
            if (isBoundary(next)) {
                return next;
            }
        }
        logger.error("findNextBoundaryPoint", "Error next point not found");
        return null;
    }

    private boolean hasAlpha(int x, int y) {
        return (img.getRGB(x, y) >>> 24 & 0xff) > 50;
    }

    /*
      * Test a point to see if the alpha is greater than 0
      */
    private boolean pointHasAlpha(int x, int y) {
        if (x < 0 || x > (img.getWidth() - 1) || y < 0 || y > (img.getHeight() - 1)) {
            return false;
        } else {
            return hasAlpha(x, y);
        }
    }

    private boolean isBoundary(Pixel point) {
        return isBoundary(point.x, point.y);
    }

    private boolean isBoundary(int x, int y) {

        // If this point doesn't have alpha we're not interested
        if (!pointHasAlpha(x, y))
            return false;

        // Look at the 8 ajoining points, if at least one has alpha then
        // it's a boundary
        for (int i = -1; i < 2; i++) {
            for (int j = -1; j < 2; j++) {
                if (!pointHasAlpha(x + i, y + j))
                    return true;//result = true;
            }
        }

        // If this point has alpha and all of the ajoining points
        // have alpha then it's not a boundary
        return false;
    }

    public ArrayList<Vertex> average( int average) {
        averagedPoints = new ArrayList<Vertex>();
        // Add the first point
        averagedPoints.add(points.get(0));

        ArrayList<Vertex> averageVertices = new ArrayList<Vertex>();

        // Loop over the next [average] vertices and
        // add the result to the array of smoothed points
        for (int i = 0; i < points.size() - average; i++) {

            averageVertices.clear();
            for (int j = 0; j < average; j++, i++) {

                averageVertices.add(points.get(i));
            }
            averagedPoints.add(average(averageVertices));
        }
        return averagedPoints;

    }
    
//    public ArrayList<Vertex> concaveFinder(){
//        ArrayList<Vertex> result = new ArrayList<Vertex>();
//        for(int i =0; i < averagedPoints.size(); i ++){
//            Vertex current = averagedPoints.get(i);
//            Vertex prev = averagedPoints.get((i - 1 < 0) ? averagedPoints.size() - 1 : i -1);
//            Vertex pprev = averagedPoints.get( i -2 <0 ? averagedPoints.size() -2 : i-2);
//            isConcave(current, prev, pprev);
//
//        }
//
//    }
//
//    private boolean isConcave(Vertex next, Vertex center, Vertex prev) {
//        if(
//                (center.x - next.x) * (center.x  - prev.x) *
//                        (center.y - next.y) * (center.y - prev.y ) < 0
//                ){
//            return true;
//        }else {
//            double xA = center.x - prev.x;
//            double yA = center.y - prev.y;
//            double xB = center.x - next.x;
//            double yB = center.y - next.y;
//            double dotProduct =xA * xB + yA* yB;
//            double lenA = Math.sqrt( xA*xA + yA*yA);
//            double lenB = Math.sqrt( xB*xB + yB*yB);
//            double angle = Math.acos(dotProduct/lenA/lenB);
//            if(angle <Math.PI/2)
//                return ;
//
//            )
//        }
//
//
//    }

    public ArrayList<Vertex> maximumCurvatureAlgorithm(float curvatureLimit){
        ArrayList<Vertex> simplifiedLine = new ArrayList<Vertex>();
        double curvature;
        boolean isFlat;
        simplifiedLine.add(averagedPoints.get(0));
        List<Vertex> curve = new ArrayList<Vertex>();
        Vertex lineStart;
        for (int i = 1; i < averagedPoints.size(); ) {
            curve.add(averagedPoints.get(i));
            curvature = 0;
            isFlat = false;
            lineStart = null;
            // Calculate the curvature
            while (curvature < curvatureLimit) {
                curve.add(averagedPoints.get(++i));
                curvature = curvature(curve);
                if (curvature < 1 && !isFlat) {
                    isFlat = true;
                    lineStart = curve.get(0);
                }
                if (curvature > 1 && isFlat) {
                    if (distance(curve.get(curve.size() - 2), lineStart) > LINE_LENGTH) {
                        simplifiedLine.add(curve.get(curve.size() - 2));
                        Vertex last = curve.get(curve.size() - 1);
                        curve.clear();
                        curve.add(last);
                        isFlat = false;
                        lineStart = null;
                        curvature = 0;
                    }
                }
                if (i == averagedPoints.size() - 1) {
                    simplifiedLine.add(averagedPoints.get(--i));
                    return simplifiedLine;
                }
            }

            simplifiedLine.add(curve.get(curve.size() / 2 + 1));
            curve = curve.subList(curve.size() / 2 + 2, curve.size());
        }
        return simplifiedLine;
    }

    private double distance(Vertex vertex, Vertex lineStart) {
        return Math.sqrt(Math.pow((vertex.x - lineStart.x), 2) + Math.pow((vertex.y - lineStart.y), 2));
    }

    private Vertex average(ArrayList<Vertex> vertices) {
        Vertex average = new Vertex(0, 0);
        for (Vertex v : vertices) {
            average = average.add(v);
        }
        average.set(average.x / vertices.size(), average.y / vertices.size());
        return average;
    }

    private Vertex midPoint(Vertex v1, Vertex v2) {
        return new Vertex((v1.x + v2.x) / 2, (v1.y + v2.y) / 2);
    }

    private double curvature(List<Vertex> curve) {

        if (curve.size() < 3) {
            return 0;
        }
        Vertex start = curve.get(0);
        Vertex end = curve.get(curve.size() - 1);
        //calculate the line ax+by +c =0
        double a, b, c;
        if (start.y == end.y) {
            b = 1;
            a = 0;
        } else {
            b = -(start.x - end.x) / (start.y - end.y);
            a = 1;
        }
        c = (b * (start.y + end.y) + a * (start.x + end.x)) / -2;

        Vertex curveMid;
        if (curve.size() % 2 == 0) {
            curveMid = midPoint(curve.get(curve.size() / 2), curve.get(curve.size() / 2 + 1));
        } else {
            curveMid = curve.get(curve.size() / 2 + 1);
        }

        return Math.abs(a * curveMid.x + b * curveMid.y + c) / Math.sqrt(a * a + b * b);
    }


}
